// SPDX-License-Identifier: GPL-2.0-only

#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/types.h>
#include <linux/jump_label.h>
#include <linux/swap.h>
#include <linux/mman.h>

DEFINE_STATIC_KEY_FALSE(nomem_warn_enabled);

static ssize_t nomem_warn_enabled_show(struct kobject *kobj,
				     struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%s\n",
			static_branch_unlikely(&nomem_warn_enabled) ? "true" : "false");
}
static ssize_t nomem_warn_enabled_store(struct kobject *kobj,
				      struct kobj_attribute *attr,
				      const char *buf, size_t count)
{
	if (!strncmp(buf, "true", 4) || !strncmp(buf, "1", 1))
		static_branch_enable(&nomem_warn_enabled);
	else if (!strncmp(buf, "false", 5) || !strncmp(buf, "0", 1))
		static_branch_disable(&nomem_warn_enabled);
	else
		return -EINVAL;

	return count;
}
static struct kobj_attribute nomem_warn_enabled_attr =
	__ATTR(nomem_warn_enabled, 0644, nomem_warn_enabled_show,
	       nomem_warn_enabled_store);

static struct attribute *overcommit_attrs[] = {
	&nomem_warn_enabled_attr.attr,
	NULL,
};

static struct attribute_group overcommit_attr_group = {
	.attrs = overcommit_attrs,
};

static int __init overcommit_init_sysfs(void)
{
	int err;
	struct kobject *overcommit_kobj;

	overcommit_kobj = kobject_create_and_add("overcommit", mm_kobj);
	if (!overcommit_kobj) {
		pr_err("failed to create overcommit kobject\n");
		return -ENOMEM;
	}
	err = sysfs_create_group(overcommit_kobj, &overcommit_attr_group);
	if (err) {
		pr_err("failed to register overcommit group\n");
		goto delete_obj;
	}
	return 0;

delete_obj:
	kobject_put(overcommit_kobj);
	return err;
}
subsys_initcall(overcommit_init_sysfs);

void nomem_warn_print_overcommit_warning(long pages, long allowed)
{
	if (!static_branch_unlikely(&nomem_warn_enabled))
		return;
	if (sysctl_overcommit_memory == OVERCOMMIT_GUESS) {
		pr_warn_ratelimited("WARN: pid=%d cmd=%s total_page=%ld pages=%ld\n"
			"WARN: Unable to allocate a new virtual mapping because of the overcommit\n",
			current->pid, current->comm,
			totalram_pages() + total_swap_pages, pages);
	} else if (sysctl_overcommit_memory == OVERCOMMIT_NEVER) {
		pr_warn_ratelimited("WARN: pid=%d cmd=%s now=%lld allowed=%ld\n"
			"WARN: Unable to allocate a new virtual mapping because of the overcommit\n",
			current->pid, current->comm,
			percpu_counter_read_positive(&vm_committed_as), allowed);
	}
}
